﻿/// <summary>
/// Collection of small utility methods and classes
/// </summary>
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;

namespace SpaceSim
{
    public static class Misc
    {
        /// <summary>
        /// Interpolates between value1 and value2 by time. 
        /// </summary>
        /// <param name="time">0.0 to 1.0</param>
        /// <param name="value1">Lower value to interpolate from</param>
        /// <param name="value2">Higher value to interpolate to</param>
        /// <returns>Interpolated value</returns>
        public static double Lerp(double time, double value1, double value2)
        {
            return value1 + time * (value2 - value1);
        }

        /// <summary>
        /// Wrapper for opening a file and immediately deserializing it as the specified type.
        /// </summary>
        /// <typeparam name="T">Type to deserialize xml file to.</typeparam>
        /// <param name="filePath">Path to xml file.</param>  
        /// <returns></returns>
        static public T XmlDeserialize<T>(string filePath)
        {
            using (FileStream streamIn = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                return (T)serializer.Deserialize(streamIn);
            }
        }

        /// <summary>
        /// Writes out the given object as an xml file to specified location. 
        ///
        /// Object must have XmlRootAttribute decorator to use this method.
        /// </summary>
        /// <param name="filePath">Path to xml file.</param>
        static public void XmlSerialize(this object obj, string filePath)
        {
            string directoryPath = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(directoryPath) && !string.IsNullOrEmpty(directoryPath))
            {
                Directory.CreateDirectory(directoryPath);
            }

            using (TextWriter writer = new StreamWriter(filePath, false, Encoding.Unicode))
            {
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                XmlSerializerNamespaces serializerNamespace = new XmlSerializerNamespaces();
                serializerNamespace.Add("", "");

                // Write out the xlast file using the empty namespace that XLAST expects.
                serializer.Serialize(writer, obj, serializerNamespace);
            }
        }

        /// <summary>
        /// Attempts to convert given object to a double using System.Convert.
        /// </summary>
        /// <param name="obj">Object function will try to convert to a double.</param>
        /// <param name="result">
        /// Where conversion value is stored. Will be NaN if conversion was not possible.
        /// </param>
        /// <return>False if conversion is not possible, true if it is.</return>
        static public bool TryConvert_ToDouble(this object obj, out double result)
        {
            try
            {
                result = Convert.ToDouble(obj);
            }
            catch (System.FormatException)
            {
                result = double.NaN;
            }

            return !double.IsNaN(result);
        }

        /// <summary>
        /// Attempts to convert given object to a string using System.Convert.
        /// </summary>
        /// <param name="obj">Object function will try to convert to a string.</param>
        /// <param name="result">
        /// Where conversion value is stored. Will be null if conversion was not possible.
        /// </param>
        /// <return>False if conversion is not possible, true if it is.</return>
        static public bool TryConvert_ToString(this object obj, out string result)
        {
            try
            {
                result = Convert.ToString(obj);
            }
            catch (System.FormatException)
            {
                result = null;
            }

            return result != null;
        }

        /// <summary>
        /// Returns true if the given object search string (coming from a glossary.xml file) has 
        /// the special character denoting the target object is found up the ownershipChain.
        /// </summary>
        /// <param name="searchName">Object function will try to convert to a string.</param>
        /// <return>True if searchString begins with '^'</return>
        static public bool ObjectSearch_IsExternalSearch(string searchName)
        {
            return !string.IsNullOrEmpty(searchName) && searchName.StartsWith("^");
        }

        /// <summary>
        /// Coerces the given searchName to an object mapping to a property either on "searchOn", 
        /// or an object up the ownershipChain. 
        /// 
        /// The returning object would be valid for coercing into an actual value for the 
        /// ConditionGroup system, or to be set by the Modifiable class.
        /// 
        /// The ownershipChain is only searched if the ObjectSearch_IsExternalSearch(searchName) is
        /// true.
        /// </summary>
        /// <param name="searchOn">
        /// First object to look for a property with a name of "searchName" on.
        /// </param>
        /// <param name="searchName">
        /// Name of property to look for a on "searchOn" or ownershipChain. "." values in searchName
        /// are fully supported.
        /// </param>
        /// <param name="ownershipChain">
        /// Stack of parents to searchOn that are checked if a match on searchOn is not found AND
        /// ObjectSearch_IsExternalSearch(searchName) is true.
        /// </param>
        /// <return>
        /// Object for the property whose name matched "searchName", or null if no match was found
        /// </return>
        static public object ObjectSearch_Get(object searchOn, string searchName, object[] ownershipChain)
        {
            bool recurseIntoOwnershipChain = searchName.StartsWith("^");
            searchName = searchName.TrimStart(new char[] { '^' });

            double resultNumber = 0.0;
            if (double.TryParse(searchName, out resultNumber))
            {
                return resultNumber;
            }

            object resultObject = _InternalObjectSearch_Get(searchName, searchOn);
            if (null == resultObject && null != ownershipChain && recurseIntoOwnershipChain)
            {
                foreach (object obj in ownershipChain)
                {
                    resultObject = _InternalObjectSearch_Get(searchName, obj);
                    if (null != resultObject)
                    {
                        break;
                    }
                }
            }

            if (null != resultObject)
            {
                return resultObject;
            }

            return searchName;
        }

        /// <summary>
        /// Checks the given object to see if it has a property of "searchName".
        /// </summary>
        /// <param name="searchName">
        /// Name of property to look for a on "obj". "." values in searchName are fully supported.
        /// </param>
        /// <return>
        /// Object for the property whose name matched "searchName", or null if no match was found
        /// </return>
        private static object _InternalObjectSearch_Get(string searchName, object obj)
        {
            string[] tokens = searchName.Split('.');
            object objResult = obj;
            for (int i = 0; i < tokens.Length && null != objResult; ++i)
            {
                PropertyInfo propInfo = objResult.GetType().GetProperty(tokens[i]);
                objResult = null != propInfo ? propInfo.GetValue(objResult, null) : null;
            }

            return objResult != obj ? objResult : null;
        }

        /// <summary>
        /// Searches for a property using the same technique as ObjectSearch_Get, and then sets it
        /// to value specified by "val".
        /// </summary>
        /// </summary>
        /// <param name="searchOn">
        /// First object to look for a property with a name of "searchName" on.
        /// </param>
        /// <param name="searchName">
        /// Name of property to look for a on "searchOn" or ownershipChain. "." values in searchName
        /// are fully supported.
        /// </param>
        /// <param name="ownershipChain">
        /// Stack of parents to searchOn that are checked if a match on searchOn is not found AND
        /// ObjectSearch_IsExternalSearch(searchName) is true.
        /// </param>
        /// <param name="val">
        /// Value set on matching property
        /// </param>
        static public void ObjectSearch_Set(this object searchOn, string searchName, object[] ownershipChain, object val)
        {
            bool recurseIntoOwnershipChain = searchName.StartsWith("^");
            searchName = searchName.TrimStart(new char[] { '^' });

            if (_InternalSearchForObject_Set(searchName, searchOn, val))
            {
                return;
            }

            if (null != ownershipChain && recurseIntoOwnershipChain)
            {
                foreach (object obj in ownershipChain)
                {
                    if (_InternalSearchForObject_Set(searchName, obj, val))
                    {
                        return;
                    }
                }
            }
        }

        /// <summary>
        /// Checks the given object to see if it has a property of "searchName" and if found assigns
        /// "val" to that property.
        /// </summary>
        /// <param name="searchName">
        /// Name of property to look for a on "obj". "." values in searchName are fully supported.
        /// </param>
        /// <param name="val">
        /// Value set on matching property
        /// </param>
        /// <return>
        /// True if match was found and "val" was assigned to it.
        /// </return>
        private static bool _InternalSearchForObject_Set(string searchName, object obj, object val)
        {
            string[] tokens = searchName.Split('.');
            object objResult = obj;
            for (int i = 0; i < tokens.Length-1 && null != objResult; ++i)
            {
                PropertyInfo currPropInfo = objResult.GetType().GetProperty(tokens[i]);
                objResult = null != currPropInfo ? currPropInfo.GetValue(objResult, null) : null;
            }

            if (objResult == null)
            {
                return false;
            }

            PropertyInfo propInfo = objResult.GetType().GetProperty(searchName);
            if (propInfo == null)
            {
                return false;
            }

            propInfo.SetValue(objResult, Convert.ChangeType(val, propInfo.PropertyType), null);
            return true;
        }
    }
}